package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 
 * date:2022/9/20 14:10
 * 描述：
 **/
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;


    @Override
    public List<SeckillGoods> findAll() {
        //  数据类型 hash; hvals key
        List<SeckillGoods> seckillGoodsList = this.redisTemplate.opsForHash().values(RedisConst.SECKILL_GOODS);
        return seckillGoodsList;
    }

    //  根据skuId 获取详情：hget key field;
    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        //  获取数据.
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());
        //  返回数据.
        return seckillGoods;
    }

    @Override
    public Result seckillOrder(Long skuId, String skuIdStr, String userId) {
        /*
        1.  校验抢购码
        2.  校验状态位: 存在map 中.
         */
        if (!skuIdStr.equals(MD5.encrypt(userId))){
            //  信息提示
            return Result.fail().message("校验抢购码失败.");
        }
        //  状态位： key = skuId value = state
        String state = (String) CacheHelper.get(skuId.toString());
        if ("0".equals(state)){
            //  提示信息
            return Result.fail().message("已售罄.");
        }else if (StringUtils.isEmpty(state)){
            //  提示信息
            return Result.fail().message("非法的状态位.");
        }else {
            //  1 说明这个商品可以秒杀.
            //  将这个用户与商品保存到队列中.
            UserRecode userRecode = new UserRecode();
            userRecode.setUserId(userId);
            userRecode.setSkuId(skuId);
            //  将这个对象放入队列中.
            this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_USER,MqConst.ROUTING_SECKILL_USER,userRecode);
            //  正确返回
            return Result.ok();
        }
    }

    @Override
    public void seckillUserOrder(UserRecode userRecode) {
         /*
        1.  判断状态位.
        2.  判断用户是否下过订单.
                setnx key value;
        3.  减库存.
                list 队列 rpop key
                出队失败： 说明没有库存，则需要通知其他兄弟节点，别秒杀！
                    publish seckillpush skuId:0;
        4.  将用户秒杀的商品等信息，保存到 redis 中. SeckillGoods 保存预下单数据.

        5.  更新redis - stockCount  ,mysql - stockCount

         */
        //  状态位： key = skuId value = state
        String state = (String) CacheHelper.get(userRecode.getSkuId().toString());
        //  此时状态位异常，不能秒杀.
        if (StringUtils.isEmpty(state) || "0".equals(state)){
            return;
        }
        //  为了保证每个用户只能购买一次，使用setnx 命令来判断！
        //  seckill:user:userId
        String userKey = RedisConst.SECKILL_USER+userRecode.getUserId();
        Boolean result = this.redisTemplate.opsForValue().setIfAbsent(userKey, userRecode.getSkuId().toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  result = true 第一次购买！ result = false 表示已经购买过了.
        if (!result){
            return;
        }

        //  减库存！ lpush key value
        //  seckill:stock:28   rpop key
        String skuIdExist = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId());
        if (StringUtils.isEmpty(skuIdExist)){
            //  没有库存了，必须要赶紧通知其他兄弟节点
            this.redisTemplate.convertAndSend("seckillpush",userRecode.getSkuId()+":0");
            return;
        }

        //  饥饿营销： 时间限制，库存限制，购买数量限制 1
        //  保存用户的预下单数据 UserRecode {userId , skuId} OrderRecode{seckillGoods,num...}  redis 中存储！
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userRecode.getUserId());
        orderRecode.setNum(1);
        orderRecode.setSeckillGoods(this.getSeckillGoods(userRecode.getSkuId()));
        orderRecode.setOrderStr(MD5.encrypt(userRecode.getUserId()+userRecode.getSkuId()));

        //  数据类型：String seckill:orders:userId orderRecode   Hash hset seckill:orders userId orderRecode;
        //  this.redisTemplate.opsForValue().set(seckill:orders:userId,orderRecode);
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userRecode.getUserId(),orderRecode);

        //  修改秒杀商品的剩余库存数:
        //  发送消息 - 异步； 直接操作 - 同步！ 先修改 redis 再修改数据库!
        /*
            先修改 redis 再修改数据库!
                update - redis  ---  success!
                update - mysql  ---  error！

            采用异步：
                update - mysql  ---  error！ rollback
                update - redis  ---  error!
                能够尽量保证mysql 与redis 数据一致性
         */
        //  异步 - 发送消息！
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK, MqConst.ROUTING_SECKILL_STOCK, userRecode.getSkuId());

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStock(Long skuId) {
        //  更新缓存： hget key field  hset key field value  等同于从数据库中获取到数据.
        SeckillGoods seckillGoodsRedis = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());

        //  第一个参数更新的内容，第二个参数更新的条件
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  设置剩余库存数量.

        seckillGoodsRedis.setStockCount(count.intValue());
        seckillGoodsRedis.setUpdateTime(new Date());

        //  数据库
        this.seckillGoodsMapper.updateById(seckillGoodsRedis);
        //  操作缓存！
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS,skuId.toString(),seckillGoodsRedis);
    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        /*
            1.  验证缓存中是否有用户Id
                    String userKey = RedisConst.SECKILL_USER+userRecode.getUserId();

            2.  判断用户是否有预下单 ---> 抢购成功 去下单
                    this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userRecode.getUserId(),orderRecode);

            3.  判断用户是否真正下过订单 ---> 抢购成功 我的订单
            4.  校验一下状态位
         */
        String userKey = RedisConst.SECKILL_USER+userId;
        Boolean exist = this.redisTemplate.hasKey(userKey);
        //  exist = true 说明这个key 存在，有用户
        if (exist){
            //  在有用户的前提下 判断是否有预下单记录.
            Boolean result = this.redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_ORDERS, userId);
            //  result = true 说明这个有预下单数据.
            if (result){
                //  提示抢购成功，去下单.
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  判断用户是否真正下过订单： 订单结算页点击提交订单时 保存到数据库，同时保存到缓存
        //  hset key field value SECKILL_ORDERS_USERS = "seckill:orders:users";  field = userId  value = orderId
        Boolean flag = this.redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_ORDERS_USERS, userId);
        //  flag = true 已经下过订单，提示 我的订单
        if (flag){
            String orderId = (String) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS_USERS, userId);
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //  判断一下状态位:
        //  状态位： key = skuId value = state
        String state = (String) CacheHelper.get(skuId.toString());
        if ("0".equals(state) || StringUtils.isEmpty(state)){
            return Result.build(null , ResultCodeEnum.SECKILL_FAIL);
        }

        //  默认排队中。
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

}